package org.jeecg.modules.tankerRegistration.controller;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.huaweicloud.sdk.ocr.v1.model.GeneralTableResult;
import com.huaweicloud.sdk.ocr.v1.model.GeneralTableWordsBlockList;
import com.huaweicloud.sdk.ocr.v1.model.RecognizeGeneralTableResponse;
import com.huaweicloud.sdk.ocr.v1.model.WordsRegionList;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.CommonUtils;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.RestUtil;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.common.util.oss.ObsBootUtil;
import org.jeecg.modules.backups.entity.Backups;
import org.jeecg.modules.backups.service.IBackupsService;
import org.jeecg.modules.gpsRecord.entity.GpsRecord;
import org.jeecg.modules.gpsRecord.service.IGpsRecordService;
import org.jeecg.modules.healthCertificateDetail.entity.HealthCertificateDetail;
import org.jeecg.modules.healthCertificateDetail.service.IHealthCertificateDetailService;
import org.jeecg.modules.identifyRecord.entity.IdentifyRecord;
import org.jeecg.modules.identifyRecord.service.IIdentifyRecordService;
import org.jeecg.modules.online.config.exception.BusinessException;
import org.jeecg.modules.system.entity.SysPermission;
import org.jeecg.modules.system.service.ISysPermissionService;
import org.jeecg.modules.tankerBlack.entity.TankerBlack;
import org.jeecg.modules.tankerBlack.service.ITankerBlackService;
import org.jeecg.modules.tankerRegistration.entity.*;
import org.jeecg.modules.tankerRegistration.scheduled.MyScheduled;
import org.jeecg.modules.tankerRegistration.service.ITankerRegistrationService;
import org.jeecg.modules.tankerRegistration.util.*;
import org.jeecg.modules.userOpenId.entity.UserOpenId;
import org.jeecg.modules.userOpenId.service.IUserOpenIdService;
import org.jeecgframework.poi.excel.ExcelExportUtil;
import org.jeecgframework.poi.excel.def.NormalExcelConstants;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.view.JeecgEntityExcelView;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.ModelAndView;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @Description: 罐车登记表
 * @Author: jeecg-boot
 * @Date: 2024-10-28
 * @Version: V1.0
 */
@Api(tags = "罐车登记表")
@RestController
@RequestMapping("/tankerRegistration/tankerRegistration")
@Slf4j
public class TankerRegistrationController extends JeecgController<TankerRegistration, ITankerRegistrationService> {
    @Value(value = "${jeecg.uploadType}")
    private String uploadType;

    @Value("${jeecg.path.upload}")
    private String upLoadPath;

    @Autowired
    private ITankerRegistrationService tankerRegistrationService;
    @Autowired
    private ITankerBlackService tankerBlackService;
    @Autowired
    private TemplateMessageUtil templateMessageUtil;
    @Autowired
    private IBackupsService backupsService;
    @Autowired
    private IUserOpenIdService userOpenIdService;
    @Autowired
    private IGpsRecordService gpsRecordService;
    @Autowired
    private IIdentifyRecordService identifyRecordService;
    @Autowired
    private IHealthCertificateDetailService healthCertificateDetailService;
    @Autowired
    private ISysPermissionService sysPermissionService;

    //太维科技公众号
//	 private static final String APPID = "wx35cfb8619f70829d";
//	 private static final String SECRET = "ad9fa5ffe510db2f88d3164c119444c1";
    //镇江中储粮公众号
    private static final String APPID = "wx026cd17aaa218445";
    private static final String SECRET = "c30815b8329dd4e79d0db6dc0f1613c8";

    // 加载 OpenCV 本地库
    static {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
    }

    /**
     * 分页列表查询
     *
     * @param tankerRegistration
     * @param pageNo
     * @param pageSize
     * @param req
     * @return
     */
    @AutoLog(value = "罐车登记表-分页列表查询")
    @ApiOperation(value = "罐车登记表-分页列表查询", notes = "罐车登记表-分页列表查询")
    @GetMapping(value = "/list")
    public Result<?> queryPageList(TankerRegistration tankerRegistration,
                                   @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
                                   @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
                                   HttpServletRequest req) {
        QueryWrapper<TankerRegistration> queryWrapper = QueryGenerator.initQueryWrapper(tankerRegistration, req.getParameterMap());
        Page<TankerRegistration> page = new Page<TankerRegistration>(pageNo, pageSize);
        IPage<TankerRegistration> pageList = tankerRegistrationService.page(page, queryWrapper);
        return Result.OK(pageList);
    }

    @AutoLog(value = "罐车登记表-销售审核三载")
    @ApiOperation(value = "罐车登记表-销售审核三载", notes = "罐车登记表-销售审核三载")
    @GetMapping(value = "/salesAuditList")
    public Result<?> salesAuditList(TankerRegistration tankerRegistration,
                                    @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
                                    @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
                                    HttpServletRequest req) {
        QueryWrapper<TankerRegistration> wrapper = QueryGenerator.initQueryWrapper(tankerRegistration, req.getParameterMap());
        wrapper.ne("production_name", "磷脂");
        Page<TankerRegistration> page = new Page<TankerRegistration>(pageNo, pageSize);
        IPage<TankerRegistration> pageList = tankerRegistrationService.page(page, wrapper);
        return Result.OK(pageList);
    }

    @AutoLog(value = "罐车登记表-分页列表查询(APP)")
    @ApiOperation(value = "罐车登记表-分页列表查询(APP)", notes = "罐车登记表-分页列表查询(APP)")
    @GetMapping(value = "/m/list")
    public Result<?> qualityList(TankerRegistration tankerRegistration,
                                 @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
                                 @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
                                 HttpServletRequest req) {
        String openId = tankerRegistration.getOpenId();
        QueryWrapper<TankerRegistration> queryWrapper = QueryGenerator.initQueryWrapper(tankerRegistration, req.getParameterMap());
        if (StringUtils.isNotBlank(openId)) {
            queryWrapper.eq("open_id", openId).or().eq("driver_open_id", openId);
        }
        Page<TankerRegistration> page = new Page<TankerRegistration>(pageNo, pageSize);
        IPage<TankerRegistration> pageList = tankerRegistrationService.page(page, queryWrapper);
        return Result.OK(pageList);
    }

    @AutoLog(value = "罐车登记表-分页列表查询(APP)")
    @ApiOperation(value = "罐车登记表-分页列表查询(APP)", notes = "罐车登记表-分页列表查询(APP)")
    @GetMapping(value = "/m/onboardRecordFormList")
    public Result<?> onboardRecordFormList(TankerRegistration tankerRegistration,
                                           @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
                                           @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
                                           HttpServletRequest req) {
        QueryWrapper<TankerRegistration> queryWrapper = QueryGenerator.initQueryWrapper(tankerRegistration, req.getParameterMap());
        queryWrapper.in("audit_status", 8, 11);
        if (tankerRegistration.getTransportStatus() == null) {
            queryWrapper.in("transport_status", 1, 3);
        }
        Page<TankerRegistration> page = new Page<TankerRegistration>(pageNo, pageSize);
        IPage<TankerRegistration> pageList = tankerRegistrationService.page(page, queryWrapper);
        return Result.OK(pageList);
    }

    @AutoLog(value = "罐车登记表-流程图显示车辆数(APP)")
    @ApiOperation(value = "罐车登记表-流程图显示车辆数(APP)", notes = "罐车登记表-流程图显示车辆数(APP)")
    @GetMapping(value = "/m/getFlowChart")
    public Result<?> getFlowChart() {
        QueryWrapper<TankerRegistration> wrapper = new QueryWrapper<>();
        wrapper.select("audit_status", "COUNT(*) as count", "transport_status");
        wrapper.groupBy("audit_status", "transport_status");
        List<Map<String, Object>> maps = tankerRegistrationService.listMaps(wrapper);

        // 使用Map来保存不同transport_status的结果
        Map<Integer, Map<String, Integer>> transportStatusToAuditCountMap = new HashMap<>();

        for (int status = 1; status <= 3; status++) { // 假设transport_status只可能是1, 2, 3
            final int currentStatus = status; // 在lambda表达式中使用的局部变量需要是final或实际上final
            Map<String, Integer> auditStatusCountMap = maps.stream()
                    .filter(m -> currentStatus == (int) m.get("transport_status"))
                    .collect(Collectors.toMap(
                            m -> String.valueOf(m.get("audit_status")),
                            m -> ((Number) m.get("count")).intValue(),
                            (oldValue, newValue) -> oldValue // 处理key冲突的情况，这里简单地保留旧值
                    ));
            transportStatusToAuditCountMap.put(status, auditStatusCountMap);
        }

        return Result.OK(transportStatusToAuditCountMap);
    }

    /**
     * 添加
     *
     * @param tankerRegistration
     * @return
     */
    @AutoLog(value = "罐车登记表-添加")
    @ApiOperation(value = "罐车登记表-添加", notes = "罐车登记表-添加")
    @PostMapping(value = "/add")
    public Result<?> add(@RequestBody TankerRegistration tankerRegistration) {
        tankerRegistrationService.save(tankerRegistration);
        return Result.OK("添加成功！");
    }

    @AutoLog(value = "罐车登记表-司机登记")
    @ApiOperation(value = "罐车登记表-司机登记", notes = "罐车登记表-司机登记")
    @PostMapping(value = "/m/add")
    public Result<?> mAdd(@RequestBody RegistrationDTO tankerRegistration) {
        TankerRegistration byPlateNo = tankerRegistrationService.getByPlateNo(tankerRegistration.getPlateNo());
        if (byPlateNo != null) {
            Integer auditStatus = byPlateNo.getAuditStatus();
            if (1 == auditStatus) {
                tankerRegistrationService.removeById(byPlateNo.getId());
            } else if (Arrays.asList(2, 4, 6, 8, 11, 12).contains(auditStatus)) {
                return Result.error("当前车辆正在流程进行中, 请完成全部流程后再登记。");
            }
        }
        TankerBlack black = tankerBlackService.getByPlateOrTrailerNo(tankerRegistration.getPlateNo(),
                tankerRegistration.getTrailerNo());
        if (black != null && black.getEnable() == 1) {
            tankerRegistration.setAuditStatus(14);//黑名单
        }
        TankerRegistration tanker = new TankerRegistration();
        BeanUtils.copyProperties(tankerRegistration, tanker);
        tanker.setCreateTime(null).setUpdateTime(null).setCreateBy(null).setUpdateBy(null).setId(null);
        tankerRegistrationService.save(tanker);
        List<HealthCertificateDetail> healthCertificateList = tankerRegistration.getHealthCertificateList();
        for (HealthCertificateDetail healthCertificate : healthCertificateList) {
            healthCertificate.setTankerRegistrationId(tanker.getId());
        }
        healthCertificateDetailService.saveBatch(healthCertificateList);
        new Thread(() -> {
            try {
                Integer productionType = tankerRegistration.getProductionType();
                Integer transportStatus = tankerRegistration.getTransportStatus();
                Integer unloadingDirection = tankerRegistration.getUnloadingDirection();
                String productionName = tankerRegistration.getProductionName();
                String plateNo = tankerRegistration.getPlateNo();
                String driverName = tankerRegistration.getDriverName();
                String phoneNum = tankerRegistration.getPhoneNum();
                String role =
                        productionType == 1 ?
                                (transportStatus == 1 ? "first_instance4" :
                                        unloadingDirection == 1 ? "first_instance1" : "first_instance2") :
                                productionType == 2 ?
                                        "first_instance3" :
                                        ("磷脂".equals(productionName) ? "first_instance5" : "first_instance4");

                List<String> openIdByRole = userOpenIdService.getOpenIdByRole(role);
                for (String openId : openIdByRole) {
                    if (2 == tankerRegistration.getTransportMode()){
                        templateMessageUtil.sendShipOA(openId, plateNo, productionName, driverName, phoneNum);
                    } else {
                        templateMessageUtil.sendCarOA(openId, plateNo, productionName, driverName, phoneNum);
                    }
                }
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }).start();
        return Result.OK("添加成功！");
    }

    @AutoLog(value = "罐车登记表-验证(app)")
    @ApiOperation(value = "罐车登记表-验证(app)", notes = "罐车登记表-验证(app)")
    @PostMapping(value = "/surveyor")
    public Result<?> surveyor(@RequestBody TankerRegistration tankerRegistration) {
        TankerRegistration byId = tankerRegistrationService.getById(tankerRegistration.getId());
        if (byId == null) {
            return Result.error("未找到对应数据");
        }
        LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        if (Arrays.asList(2, 3).contains(tankerRegistration.getAuditStatus())) {
            tankerRegistration.setAuditUsername(user.getUsername());
        }
        tankerRegistrationService.updateById(tankerRegistration);
        new Thread(() -> {
            try {
                sendMessage(tankerRegistration);
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }).start();
        return Result.OK("操作成功！", null);
    }

    @AutoLog(value = "罐车登记表-验证(app不带token)")
    @ApiOperation(value = "罐车登记表-验证(app不带token)", notes = "罐车登记表-验证(app不带token)")
    @PostMapping(value = "/m/surveyor")
    public Result<?> mSurveyor(@RequestBody TankerRegistration tankerRegistration) {
        TankerRegistration byId = tankerRegistrationService.getById(tankerRegistration.getId());
        if (byId == null) {
            return Result.error("未找到对应数据");
        }
        LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        if (Arrays.asList(2, 3).contains(tankerRegistration.getAuditStatus())) {
            tankerRegistration.setAuditUsername(user.getUsername());
        }
        tankerRegistrationService.updateById(tankerRegistration);
        new Thread(() -> {
            try {
                sendMessage(tankerRegistration);
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }).start();
        return Result.OK("操作成功！", null);
    }

    @AutoLog(value = "罐车登记表-三载审核(web)")
    @ApiOperation(value = "罐车登记表-三载审核(web)", notes = "罐车登记表-三载审核(web)")
    @PostMapping(value = "/triCarryExamine")
    public Result<?> triCarryExamine(@RequestBody TankerRegistration tankerRegistration) {
        TankerRegistration byId = tankerRegistrationService.getById(tankerRegistration.getId());
        if (byId == null) {
            return Result.error("未找到对应数据");
        }
        LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        Integer auditStatus = tankerRegistration.getAuditStatus();
        List<SysPermission> sysPermissions = sysPermissionService.queryByUser(user.getUsername());
        if (Arrays.asList(2, 3).contains(auditStatus)) {
            boolean hasPermission = sysPermissions != null && sysPermissions.stream()
                    .anyMatch(p -> "tanker:examine1".equals(p.getPerms()));
            if (!hasPermission) {
                return Result.error("没有初审权限");
            }
            tankerRegistration.setAuditTime1(new Date());
            tankerRegistration.setAuditUsername(user.getUsername());
            tankerRegistration.setAuditName1(user.getRealname());
        } else if (Arrays.asList(4, 5).contains(auditStatus)) {
            boolean hasPermission = sysPermissions != null && sysPermissions.stream()
                    .anyMatch(p -> "tanker:examine2".equals(p.getPerms()));
            if (!hasPermission) {
                return Result.error("没有二审权限");
            }
            tankerRegistration.setAuditTime2(new Date());
            tankerRegistration.setAuditName2(user.getRealname());
        }
        tankerRegistrationService.updateById(tankerRegistration);
        new Thread(() -> {
            try {
                sendMessage(tankerRegistration);
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }).start();
        return Result.OK("操作成功！", null);
    }

    public void sendMessage(TankerRegistration tankerRegistration) {
        Integer auditStatus = tankerRegistration.getAuditStatus();
        Integer productionType = tankerRegistration.getProductionType();
        Integer transportStatus = tankerRegistration.getTransportStatus();
        Integer transportMode = tankerRegistration.getTransportMode();
        Integer unloadingDirection = tankerRegistration.getUnloadingDirection();
        String productionName = tankerRegistration.getProductionName();
        String plateNo = tankerRegistration.getPlateNo();
        String driverName = tankerRegistration.getDriverName();
        String phoneNum = tankerRegistration.getPhoneNum();
        if (1 == auditStatus) {
            String role = 2 == unloadingDirection ? "first_instance2"
                    : 2 == productionType ? "first_instance3"
                    : 2 == transportStatus ? "first_instance1"
                    : "磷脂".equals(productionName) ? "first_instance5" : "first_instance4";
            log.info("即将要推送的角色: {}",  role);
            List<String> openIdByRole = userOpenIdService.getOpenIdByRole(role);
            for (String openId : openIdByRole) {
                if (2 == transportMode){
                    templateMessageUtil.sendShipOA(openId, plateNo, productionName, driverName, phoneNum);
                } else {
                    templateMessageUtil.sendCarOA(openId, plateNo, productionName, driverName, phoneNum);
                }
            }
        } else if (2 == auditStatus) {
            String role = 2 == unloadingDirection && 1 == productionType
                    && 2 == transportStatus && 1 == transportMode ? "second_instance2" : "second_instance1";
            List<String> openIdByRole = userOpenIdService.getOpenIdByRole(role);
            for (String openId : openIdByRole) {
                if (2 == transportMode){
                    templateMessageUtil.sendShipOA(openId, plateNo, productionName, driverName, phoneNum);
                } else {
                    templateMessageUtil.sendCarOA(openId, plateNo, productionName, driverName, phoneNum);
                }
            }
        } else if (3 == auditStatus || 4 == auditStatus || 5 == auditStatus) {
            String reason = 4 == auditStatus ? "三载审核已通过" : "驳回原因:" + tankerRegistration.getRejectReason();
            if (reason.length() > 20) reason = reason.substring(0, 17) + "...";// 确保 reason 不超过20个字符
            if (2 == transportMode){
                templateMessageUtil.shipRegister(tankerRegistration.getOpenId(), plateNo,
                        transportStatus, reason, tankerRegistration.getCreateTime());
            } else {
                templateMessageUtil.carRegister(tankerRegistration.getOpenId(), plateNo,
                        transportStatus, reason, tankerRegistration.getCreateTime());
            }
            if (5 == auditStatus) {
                String openId = userOpenIdService.getByUserName(tankerRegistration.getAuditUsername());
                if (2 == transportMode){
                    templateMessageUtil.shipRegister(openId, plateNo,
                            transportStatus, reason, tankerRegistration.getCreateTime());
                } else {
                    templateMessageUtil.carRegister(openId, plateNo,
                            transportStatus, reason, tankerRegistration.getCreateTime());
                }
            }
            if (4 == auditStatus) {
                String role = 2 == unloadingDirection && 1 == productionType
                        && 2 == transportStatus && 1 == transportMode ? "test_tanker2" : "test_tanker1";
                List<String> openIdByRole = userOpenIdService.getOpenIdByRole(role);
                for (String openId : openIdByRole) {
                    if (2 == transportMode){
                        templateMessageUtil.sendShipOA(openId, plateNo, productionName, driverName, phoneNum);
                    } else {
                        templateMessageUtil.sendCarOA(openId, plateNo, productionName, driverName, phoneNum);
                    }
                }
            }
        } else if (6 == auditStatus) {
            String role = 2 == unloadingDirection && 1 == productionType
                    && 2 == transportStatus && 1 == transportMode ? "quality_check1" : "quality_check";
            List<String> openIdByRole = userOpenIdService.getOpenIdByRole(role);
            for (String openId : openIdByRole) {
                if (2 == transportMode){
                    templateMessageUtil.sendShipOA(openId, plateNo, productionName, driverName, phoneNum);
                } else {
                    templateMessageUtil.sendCarOA(openId, plateNo, productionName, driverName, phoneNum);
                }
            }
        } else if (8 == auditStatus || 12 == auditStatus) {
            String message = 12 == auditStatus ? "请重新上传" : "待上传铅封";
            if (2 == transportMode){
                templateMessageUtil.sendShipLeadSealing(tankerRegistration.getOpenId(), plateNo,
                        driverName, message);
            } else {
                templateMessageUtil.sendCarLeadSealing(tankerRegistration.getOpenId(), plateNo,
                        driverName, message);
            }
        } else if (11 == auditStatus) {
            String role = 1 == transportStatus ? "onboard_record2" : 2 == unloadingDirection ? "onboard_record1" : "onboard_record";
            List<String> openIdByRole = userOpenIdService.getOpenIdByRole(role);
            for (String openId : openIdByRole) {
                if (2 == transportMode){
                    templateMessageUtil.sendShipOA(openId, plateNo, productionName, driverName, phoneNum);
                } else {
                    templateMessageUtil.sendCarOA(openId, plateNo, productionName, driverName, phoneNum);
                }
            }
        }
    }

    /**
     * 编辑
     *
     * @param tankerRegistration
     * @return
     */
    @AutoLog(value = "罐车登记表-编辑")
    @ApiOperation(value = "罐车登记表-编辑", notes = "罐车登记表-编辑")
    @PutMapping(value = "/edit")
    public Result<?> edit(@RequestBody TankerRegistration tankerRegistration) {
        tankerRegistrationService.updateById(tankerRegistration);
        return Result.OK("编辑成功!");
    }

    /**
     * 通过id删除
     *
     * @param id
     * @return
     */
    @AutoLog(value = "罐车登记表-通过id删除")
    @ApiOperation(value = "罐车登记表-通过id删除", notes = "罐车登记表-通过id删除")
    @DeleteMapping(value = "/delete")
    public Result<?> delete(@RequestParam(name = "id", required = true) String id) {
        tankerRegistrationService.removeById(id);
        return Result.OK("删除成功!");
    }

    /**
     * 批量删除
     *
     * @param ids
     * @return
     */
    @AutoLog(value = "罐车登记表-批量删除")
    @ApiOperation(value = "罐车登记表-批量删除", notes = "罐车登记表-批量删除")
    @DeleteMapping(value = "/deleteBatch")
    public Result<?> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
        this.tankerRegistrationService.removeByIds(Arrays.asList(ids.split(",")));
        return Result.OK("批量删除成功!");
    }

    /**
     * 通过id查询
     *
     * @param id
     * @return
     */
    @AutoLog(value = "罐车登记表-通过id查询")
    @ApiOperation(value = "罐车登记表-通过id查询", notes = "罐车登记表-通过id查询")
    @GetMapping(value = "/queryById")
    public Result<?> queryById(@RequestParam(name = "id", required = true) String id) {
        TankerRegistration tankerRegistration = tankerRegistrationService.getById(id);
        if (tankerRegistration == null) {
            return Result.error("未找到对应数据");
        }
        return Result.OK(tankerRegistration);
    }

    @AutoLog(value = "罐车登记表-通过openId查询最新记录")
    @ApiOperation(value = "罐车登记表-通过openId查询最新记录", notes = "罐车登记表-通过openId查询最新记录")
    @GetMapping(value = "/m/getByOpenId")
    public Result<?> getByOpenId(@RequestParam(name = "openId") String openId) {
        TankerRegistration tankerRegistration = tankerRegistrationService.getByOpenId(openId);
        if (tankerRegistration == null) {
            return Result.error("未找到对应数据");
        }
        return Result.OK(tankerRegistration);
    }

    @AutoLog(value = "罐车登记表-通过车牌号查询最新记录")
    @ApiOperation(value = "罐车登记表-通过车牌号查询最新记录", notes = "罐车登记表-通过车牌号查询最新记录")
    @GetMapping(value = "/m/getByPlateNo")
    public Result<?> getByPlateNo(@RequestParam(name = "plateNo") String plateNo) {
        TankerRegistration tankerRegistration = tankerRegistrationService.getByPlateNo(plateNo);
        if (tankerRegistration == null) {
            return Result.error("未找到对应数据");
        }
        return Result.OK(tankerRegistration);
    }

    @AutoLog(value = "罐车登记表-获取主界面未读消息")
    @ApiOperation(value = "罐车登记表-获取主界面未读消息", notes = "罐车登记表-获取主界面未读消息")
    @GetMapping(value = "/m/getUnread")
    public Result<?> getUnread() {
        LambdaQueryWrapper<TankerRegistration> wrapper = Wrappers.lambdaQuery();
        wrapper.in(TankerRegistration::getAuditStatus, 1, 2, 4, 6, 8, 11, 12);
        List<TankerRegistration> list = tankerRegistrationService.list(wrapper);
        // 定义所有可能的键及其初始值
        Map<String, Integer> initialMap = new HashMap<>();
        initialMap.put("物流储运部初审", 0);
        initialMap.put("销售初审(中、小包装)", 0);
        initialMap.put("销售初审(散油)", 0);
        initialMap.put("销售初审(豆粕)", 0);
        initialMap.put("综合管理部初审", 0);
        initialMap.put("物流储运部二审", 0);
        initialMap.put("质量管理部二审", 0);
        initialMap.put("物流储运部查验", 0);
        initialMap.put("质量管理部查验", 0);
        initialMap.put("取样检验(油脂)", 0);
        initialMap.put("取样检验(包装油)", 0);
        initialMap.put("随车装运记录(油脂)", 0);
        initialMap.put("随车装运记录(包装油)", 0);
        initialMap.put("随车装运记录(计量)", 0);
        initialMap.put("等待司机铅封", 0);
        Map<String, Integer> map = new HashMap<>(initialMap);
        for (TankerRegistration tankerRegistration : list) {
            Integer status = tankerRegistration.getAuditStatus();
            Integer transportStatus = tankerRegistration.getTransportStatus();
            Integer productionType = tankerRegistration.getProductionType();
            Integer unloadingDirection = tankerRegistration.getUnloadingDirection();
            Integer transportMode = tankerRegistration.getTransportMode();
            String productionName = tankerRegistration.getProductionName();
            String key = getStatusKey(status, transportStatus, productionType,
                    unloadingDirection, transportMode, productionName);
            if (key != null) {
                map.compute(key, (k, v) -> (v == null) ? 0 : v + 1);
            }
        }
        return Result.OK(map);
    }

    private static String getStatusKey(int status, int transportStatus,
                                       int productionType, int unloadingDirection,
                                       int transportMode, String productionName) {
        switch (status) {
            case 1:
                return transportStatus == 2 && productionType == 1 && unloadingDirection == 1 ? "物流储运部初审" :
                        transportStatus == 2 && transportMode == 1 && productionType == 2 ? "综合管理部初审" :
                                unloadingDirection == 2 ? "销售初审(中、小包装)" :
                                        "磷脂".equals(productionName) ? "销售初审(豆粕)" : "销售初审(散油)";
            case 2:
                return unloadingDirection == 1 ? "物流储运部二审" : "质量管理部二审";
            case 4:
                return unloadingDirection == 1 ? "物流储运部查验" : "质量管理部查验";
            case 6:
                return unloadingDirection == 1 ? "取样检验(油脂)" : "取样检验(包装油)";
            case 8:
            case 12:
                return "等待司机铅封";
            case 11:
                return transportStatus == 2 ? unloadingDirection == 1 ? "随车装运记录(油脂)" : "随车装运记录(包装油)" : "随车装运记录(计量)";
            default:
                return null;
        }
    }

    /**
     * 导出excel
     *
     * @param request
     * @param tankerRegistration
     */
    @RequestMapping(value = "/exportXls")
    public ModelAndView exportXls(HttpServletRequest request, TankerRegistration tankerRegistration) {
        return super.exportXls(request, tankerRegistration, TankerRegistration.class, "罐车登记表");
    }

    /**
     * 导出发货台账(提货)
     *
     * @param request
     * @param tankerRegistration
     */
    @RequestMapping(value = "/getTakeDeliveryLedger")
    public ModelAndView getTakeDeliveryLedger(HttpServletRequest request, TankerRegistration tankerRegistration) {
        QueryWrapper<TankerRegistration> queryWrapper = QueryGenerator.initQueryWrapper(tankerRegistration, request.getParameterMap());
        queryWrapper.eq("transport_status", 1);
        List<TankerRegistration> pageList = tankerRegistrationService.list(queryWrapper);
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        List<TankerRegistration> exportList = null;
        String selections = request.getParameter("selections");
        if (oConvertUtils.isNotEmpty(selections)) {
            List<String> selectionList = Arrays.asList(selections.split(","));
            exportList = pageList.stream().filter(item -> selectionList.contains(getId(item))).collect(Collectors.toList());
        } else {
            exportList = pageList;
        }
        List<TakeDeliveryLedgerVO> collect = exportList.stream().map(tanker -> {
            TakeDeliveryLedgerVO vo = new TakeDeliveryLedgerVO();
            BeanUtils.copyProperties(tanker, vo);
            return vo;
        }).collect(Collectors.toList());
        ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
        mv.addObject(NormalExcelConstants.FILE_NAME, "发货台账");
        mv.addObject(NormalExcelConstants.CLASS, TakeDeliveryLedgerVO.class);
        ExportParams exportParams = new ExportParams("发货台账", "导出人:" + sysUser.getRealname(), "发货台账");
        exportParams.setImageBasePath(upLoadPath);
        mv.addObject(NormalExcelConstants.PARAMS, exportParams);
        mv.addObject(NormalExcelConstants.DATA_LIST, collect);
        return mv;
    }

    /**
     * 导出收货台账(送货)
     *
     * @param request
     * @param tankerRegistration
     */
    @RequestMapping(value = "/getDeliveryLedger")
    public ModelAndView getDeliveryLedger(HttpServletRequest request, TankerRegistration tankerRegistration) {
        QueryWrapper<TankerRegistration> queryWrapper = QueryGenerator.initQueryWrapper(tankerRegistration, request.getParameterMap());
        queryWrapper.eq("transport_status", 2);
        List<TankerRegistration> pageList = tankerRegistrationService.list(queryWrapper);
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        List<TankerRegistration> exportList = null;
        String selections = request.getParameter("selections");
        if (oConvertUtils.isNotEmpty(selections)) {
            List<String> selectionList = Arrays.asList(selections.split(","));
            exportList = pageList.stream().filter(item -> selectionList.contains(getId(item))).collect(Collectors.toList());
        } else {
            exportList = pageList;
        }
        List<DeliveryLedgerVO> collect = exportList.stream().map(tanker -> {
            DeliveryLedgerVO vo = new DeliveryLedgerVO();
            BeanUtils.copyProperties(tanker, vo);
            return vo;
        }).collect(Collectors.toList());
        ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
        mv.addObject(NormalExcelConstants.FILE_NAME, "收货台账");
        mv.addObject(NormalExcelConstants.CLASS, DeliveryLedgerVO.class);
        ExportParams exportParams = new ExportParams("收货台账", "导出人:" + sysUser.getRealname(), "收货台账");
        exportParams.setImageBasePath(upLoadPath);
        mv.addObject(NormalExcelConstants.PARAMS, exportParams);
        mv.addObject(NormalExcelConstants.DATA_LIST, collect);
        return mv;
    }

    /**
     * 导出液袋台账
     *
     * @param request
     * @param tankerRegistration
     */
    @RequestMapping(value = "/getLiquidBagLedger")
    public ModelAndView getLiquidBagLedger(HttpServletRequest request, TankerRegistration tankerRegistration) {
        QueryWrapper<TankerRegistration> queryWrapper = QueryGenerator.initQueryWrapper(tankerRegistration, request.getParameterMap());
        queryWrapper.eq("transport_status", 3);
        List<TankerRegistration> pageList = tankerRegistrationService.list(queryWrapper);
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        List<TankerRegistration> exportList = null;
        String selections = request.getParameter("selections");
        if (oConvertUtils.isNotEmpty(selections)) {
            List<String> selectionList = Arrays.asList(selections.split(","));
            exportList = pageList.stream().filter(item -> selectionList.contains(getId(item))).collect(Collectors.toList());
        } else {
            exportList = pageList;
        }
        List<LiquidBagLedgerVO> collect = exportList.stream().map(tanker -> {
            LiquidBagLedgerVO vo = new LiquidBagLedgerVO();
            BeanUtils.copyProperties(tanker, vo);
            return vo;
        }).collect(Collectors.toList());
        ModelAndView mv = new ModelAndView(new JeecgEntityExcelView());
        mv.addObject(NormalExcelConstants.FILE_NAME, "液袋台账");
        mv.addObject(NormalExcelConstants.CLASS, LiquidBagLedgerVO.class);
        ExportParams exportParams = new ExportParams("液袋台账", "导出人:" + sysUser.getRealname(), "液袋台账");
        exportParams.setImageBasePath(upLoadPath);
        mv.addObject(NormalExcelConstants.PARAMS, exportParams);
        mv.addObject(NormalExcelConstants.DATA_LIST, collect);
        return mv;
    }

    /**
     * 获取对象ID
     *
     * @return
     */
    private String getId(TankerRegistration item) {
        try {
            return PropertyUtils.getProperty(item, "id").toString();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 通过excel导入数据
     *
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
    public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
        return super.importExcel(request, response, TankerRegistration.class);
    }

    @AutoLog(value = "罐车登记表-使用笔迹")
    @ApiOperation(value = "罐车登记表-使用笔迹", notes = "罐车登记表-使用笔迹")
    @PostMapping(value = "/useHandwriting")
    public Result<?> useHandwriting(@RequestParam(name = "userId") String userId,
                                    @RequestParam(name = "text") String  text) {
        try {
            UserOpenId byUserId = userOpenIdService.getByUserId(userId);
            if (byUserId == null || StringUtils.isBlank(byUserId.getHandwriting())) {
                return Result.error("无可用签名笔迹,请先维护签名笔迹");
            }
            // 构建完整 URL
            String imageUrl = "https://zclzj-tanker.obs.cn-north-4.myhuaweicloud.com/20" + byUserId.getHandwriting();
            // 下载图片
            RestTemplate restTemplate = new RestTemplate();
            ResponseEntity<byte[]> response = restTemplate.getForEntity(imageUrl, byte[].class);
            byte[] imageBytes = response.getBody();
            if (imageBytes == null) {
                return Result.error("无法下载图片");
            }

            // 动态获取 MIME 类型
            String contentType = getContentType(byUserId.getHandwriting());
            // 使用 ByteArrayMultipartFile 包装下载的图片
            MultipartFile downloadedFile = new ByteArrayMultipartFile(
                    "downloaded",
                    "downloaded_image" + extractFileExtension(byUserId.getHandwriting()),
                    contentType,
                    imageBytes
            );
            // 添加水印
            MultipartFile watermarkedFile = addWatermarkToImage(downloadedFile, text);
            // 上传文件
            String bizPath = DateUtil.formatDate(new Date());
            String savePath = CommonUtils.upload(watermarkedFile, bizPath, uploadType);
            return Result.OK(savePath.length() > 56 ? savePath.substring(56) : savePath);
        } catch (Exception e) {
            log.error("手写文件处理异常", e);
            return Result.error("处理失败：" + e.getMessage());
        }
    }

    /**
     * 文件上传统一方法
     *
     * @param request 请求
     * @return Result
     */
    @AutoLog(value = "罐车登记表-上传照片")
    @ApiOperation(value = "罐车登记表-上传照片", notes = "罐车登记表-上传照片")
    @PostMapping(value = "/m/upload")
    public Result<?> upload(HttpServletRequest request) throws IOException {
        Result<Object> result = new Result<>();
        String savePath = "";
        String bizPath = DateUtil.formatDate(new Date());
        MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
        MultipartFile file = multipartRequest.getFile("file");// 获取上传文件对象
        String name = request.getParameter("name");
        String text = request.getParameter("text");
        String type = request.getParameter("type");
        if (file != null) {
//            if ("1".equals(type)) {
//                double focusMeasure = calculateFocusMeasure(file);
//                double threshold = 2.5;
//                if (focusMeasure < threshold) {
//                    result.setMessage(getClarityRecommendation(focusMeasure));
//                    result.setSuccess(false);
//                    return result;
//                }
//            }
            if ("66".equals(name)) {
                file = addWatermarkToImage(file, "中储粮镇江粮油有限公司");
            } else if (StringUtils.isNotBlank(text)) {
                file = addWatermarkToImage(file, text);
            }
            if ("99".equals(name) || "98".equals(name)) {
                MultipartFile multipartFile = saveFileWithoutWaterMark(file);
                savePath = CommonUtils.upload(multipartFile, bizPath, uploadType);
            } else {
                MultipartFile multipartFile = saveFileWithoutWaterMark1(file);
                savePath = CommonUtils.upload(multipartFile, bizPath, uploadType);
            }
        }
        if (oConvertUtils.isNotEmpty(savePath)) {
            result.setMessage(savePath.length() > 56 ? savePath.substring(56) : savePath);
            result.setSuccess(true);
            if ("99".equals(name) || "98".equals(name)) {//三载文件
                RecognizeGeneralTableResponse ocr = HuaweiCloudUtil.recognizeGeneralTable(savePath);
                List<IdentifyRecord> identifyRecords = "99".equals(name) ? handleDocumentationTableDataOfOcr(ocr) : handleTableDataOfOcr(ocr);
                try {
                    identifyRecordService.saveBatch(identifyRecords);
                } catch (Exception e) {
                    log.error("识别记录保存失败", e);
                }
                identifyRecords.forEach(item -> {
                    item.setShipmentTime(convertToStandardFormat(item.getShipmentTime()));
                    item.setUnloadingTime(convertToStandardFormat(item.getUnloadingTime()));
                });
                result.setResult(identifyRecords);
            }
//            else if ("60".equals(name)) {//身份证
//                RecognizeIdCardResponse recognizeIdCardResponse = HuaweiCloudUtil.recognizeIdCard(savePath);
//                if (recognizeIdCardResponse != null){
//                    result.setResult(recognizeIdCardResponse.getResult());
//                }
//            } else if ("61".equals(name)) {//行驶证
//                RecognizeVehicleLicenseResponse recognizeVehicleLicenseResponse = HuaweiCloudUtil.recognizeVehicleLicense(savePath);
//                if (recognizeVehicleLicenseResponse != null){
//                    result.setResult(recognizeVehicleLicenseResponse.getResult());
//                }
//            }
        } else {
            result.setMessage("上传失败！");
            result.setSuccess(false);
        }
        return result;
    }

    /**
     * 拉普拉斯 计算图像清晰度指标（标准差）
     *
     * @param imageFile 输入的 MultipartFile 对象
     * @return 标准差
     * @throws IOException 如果文件读取失败
     */
    public static double calculateFocusMeasure(MultipartFile imageFile) throws IOException {
        try (InputStream inputStream = imageFile.getInputStream()) {
            // 1. 将 MultipartFile 转换为 Mat 对象
            byte[] bytes = imageFile.getBytes();
            Mat src = Imgcodecs.imdecode(new MatOfByte(bytes), Imgcodecs.IMREAD_UNCHANGED);
            if (src.empty()) throw new IOException("无法加载图像，请检查文件内容");
            // 2. 转换为灰度图
            Mat gray = new Mat();
            Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
            // 3. 应用拉普拉斯算子（注意：输出类型为 CV_64F，支持浮点运算）
            Mat laplacian = new Mat();
            Imgproc.Laplacian(gray, laplacian, CvType.CV_64F);
            // 4. 计算方差（注意：meanStdDev 返回的是标准差，需平方得到方差）
            MatOfDouble stdDev = new MatOfDouble();
            Core.meanStdDev(laplacian, new MatOfDouble(), stdDev);
            // 6. 释放资源
            gray.release();
            laplacian.release();
            src.release();

            return stdDev.get(0, 0)[0];
        } catch (Exception e) {
            throw new IOException("图像处理失败: " + e.getMessage(), e);
        }
    }

    /**
     * 清晰度问题诊断建议
     */
    private String getClarityRecommendation(double focusMeasure) {
        if (focusMeasure < 2.0) {
            return "【严重模糊】请重新拍摄，确保：\n1. 手持稳定\n2. 充足光线\n3. 文字区域对焦准确";
        } else if (focusMeasure < 3.5) {
            return "【轻度模糊】建议：\n1. 使用手机专业模式\n2. 拍摄时保持30cm距离\n3. 避免逆光";
        }
        return "清晰度合格";
    }

    /**
     * Tenengrad梯度法 检测 MultipartFile 是否模糊
     *
     * @param imageFile 输入的 MultipartFile 对象
     * @param threshold 方差阈值（建议 1000.0~5000.0）
     * @return 如果模糊返回 true，否则 false
     * @throws IOException 如果文件读取失败
     */
    public static boolean isImageBlurredWithTenengrad(MultipartFile imageFile, double threshold) throws IOException {
        try (InputStream inputStream = imageFile.getInputStream()) {
            // 1. 将 MultipartFile 转换为 Mat 对象
            byte[] bytes = imageFile.getBytes();
            Mat src = Imgcodecs.imdecode(new MatOfByte(bytes), Imgcodecs.IMREAD_UNCHANGED);

            if (src.empty()) {
                throw new IOException("无法加载图像，请检查文件内容");
            }

            // 转灰度图
            Mat gray = new Mat();
            Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);

            // Sobel算子计算梯度
            Mat gradX = new Mat(), gradY = new Mat();
            Imgproc.Sobel(gray, gradX, CvType.CV_64F, 1, 0);
            Imgproc.Sobel(gray, gradY, CvType.CV_64F, 0, 1);

            // 计算梯度幅值平方和
            Mat gradMagnitude = new Mat();
            Core.magnitude(gradX, gradY, gradMagnitude);
            Scalar total = Core.sumElems(gradMagnitude);
            double focusMeasure = total.val[0];

            // 释放资源
            gray.release();
            gradX.release();
            gradY.release();
            gradMagnitude.release();

            return focusMeasure < threshold;
        } catch (Exception e) {
            throw new IOException("图像处理失败: " + e.getMessage(), e);
        }

    }

    public List<IdentifyRecord> handleDocumentationTableDataOfOcr(RecognizeGeneralTableResponse response) {
        if (response == null) {
            return null;
        }
        List<IdentifyRecord> resultList = new ArrayList<>();
        try {
            GeneralTableResult result = response.getResult();
            List<WordsRegionList> wordsRegionList = result.getWordsRegionList();
            List<String> currentRow = new ArrayList<>(); // 临时存储当前行数据
            boolean isHeaderSkipped = false; // 标记是否已跳过表头
            for (WordsRegionList wordsRegion : wordsRegionList) {
                if ("table".equalsIgnoreCase(wordsRegion.getType())) {
                    List<GeneralTableWordsBlockList> wordsBlockList = wordsRegion.getWordsBlockList();
                    for (int i = 0; i < wordsBlockList.size(); i++) {
                        GeneralTableWordsBlockList wordsBlock = wordsBlockList.get(i);
                        String s = wordsBlock.getWords().replaceAll("\n", "");
                        // 收集当前行数据
                        currentRow.add(s);
                        // 每7列处理一次（表格列数）
                        if ((i + 1) % 7 == 0) { // i从0开始，+1后取模
                            if (isHeaderSkipped) {
                                // 从第二行开始处理（跳过表头）
                                IdentifyRecord identifyRecord = new IdentifyRecord()
                                        .setSerialNumber(currentRow.get(0))
                                        .setShipmentTime(currentRow.get(1))
                                        .setProductType(currentRow.get(2))
                                        .setGeneticallyModified(currentRow.get(3))
                                        .setPickupLocation(currentRow.get(4))
                                        .setDeliveryLocation(currentRow.get(5))
                                        .setUnloadingTime(currentRow.get(6));
                                resultList.add(identifyRecord);
                            } else {
                                // 标记已处理表头
                                isHeaderSkipped = true;
                            }
                            currentRow.clear(); // 清空当前行
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("ocr数据处理异常", e);
        }
        return resultList;
    }

    public List<IdentifyRecord> handleTableDataOfOcr(RecognizeGeneralTableResponse response) {
        if (response == null) {
            return null;
        }
        List<IdentifyRecord> resultList = new ArrayList<>();
        try {
            GeneralTableResult result = response.getResult();
            List<WordsRegionList> wordsRegionList = result.getWordsRegionList();
            List<String> currentRow = new ArrayList<>(); // 临时存储当前行数据
            boolean isHeaderSkipped = false; // 标记是否已跳过表头
            for (WordsRegionList wordsRegion : wordsRegionList) {
                if ("table".equalsIgnoreCase(wordsRegion.getType())) {
                    List<GeneralTableWordsBlockList> wordsBlockList = wordsRegion.getWordsBlockList();
                    for (int i = 0; i < wordsBlockList.size(); i++) {
                        GeneralTableWordsBlockList wordsBlock = wordsBlockList.get(i);
                        String s = wordsBlock.getWords().replaceAll("\n", "");
                        // 收集当前行数据
                        currentRow.add(s);
                        // 每7列处理一次（表格列数）
                        if ((i + 1) % 6 == 0) { // i从0开始，+1后取模
                            if (isHeaderSkipped) {
                                // 从第二行开始处理（跳过表头）
                                IdentifyRecord identifyRecord = new IdentifyRecord()
                                        .setSerialNumber(currentRow.get(0))
                                        .setShipmentTime(currentRow.get(1))
                                        .setProductType(currentRow.get(2))
                                        .setPickupLocation(currentRow.get(3))
                                        .setDeliveryLocation(currentRow.get(4))
                                        .setUnloadingTime(currentRow.get(5));
                                resultList.add(identifyRecord);
                            } else {
                                // 标记已处理表头
                                isHeaderSkipped = true;
                            }
                            currentRow.clear(); // 清空当前行
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("ocr数据处理异常", e);
        }
        return resultList;
    }

    public MultipartFile saveFileWithoutWaterMark1(MultipartFile file) {
        try {
            // 文件大小小于100KB时不压缩
            if (file.getSize() < (1024 * 1024 * 0.1)) {
                return file;
            }

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            // 100KB - 1M 的
            if (file.getSize() <= (1024 * 1024)) {
                Thumbnails.of(file.getInputStream()).scale(0.9f).outputQuality(0.9f).toOutputStream(outputStream);
            }
            // 1 - 2M 的
            else if ((1024 * 1024) < file.getSize() && file.getSize() <= (1024 * 1024 * 2)) {
                Thumbnails.of(file.getInputStream()).scale(0.8f).outputQuality(0.8f).toOutputStream(outputStream);
            }
            // 2M 以上的
            else if ((1024 * 1024 * 2) < file.getSize()) {
                Thumbnails.of(file.getInputStream()).scale(0.7f).outputQuality(0.7f).toOutputStream(outputStream);
            }

            byte[] bytes = outputStream.toByteArray();
            // 检查压缩后的数据是否为空
            if (bytes.length == 0) {
                log.warn("压缩后的数据为空，返回原始文件，源文件大小:{}", file.getSize());
                return file;
            }

            InputStream inputStream = new ByteArrayInputStream(bytes);
            // 转换城新的 MultipartFile 对象
            MultipartFile fileInfos = new MockMultipartFile(Objects.requireNonNull(file.getOriginalFilename()), file.getOriginalFilename(), file.getContentType(), inputStream);
            log.info("源文件大小:{}压缩后的大小:{}", file.getSize(), fileInfos.getSize());
            return fileInfos;

        } catch (Exception e) {
            log.error("图片压缩异常，返回原始文件，源文件大小:{}，异常信息:{}", file.getSize(), e.getMessage(), e);
        }
        return file;
    }

    public MultipartFile saveFileWithoutWaterMark(MultipartFile file) {
        try {
            // 检查是否为空文件
            if (file == null || file.isEmpty()) {
                return file;
            }

            // 获取原始文件信息
            String originalFilename = Objects.requireNonNull(file.getOriginalFilename());
            String contentType = file.getContentType();
            long originalSize = file.getSize();

            // 检查是否为图片
            if (!contentType.startsWith("image/")) {
                return file;
            }

            // 读取原始图片的像素尺寸
            BufferedImage originalImage = ImageIO.read(file.getInputStream());
            int originalWidth = originalImage.getWidth();
            int originalHeight = originalImage.getHeight();

            // 检查像素尺寸是否符合要求
            boolean isWidthValid = originalWidth >= 15 && originalWidth <= 8192;
            boolean isHeightValid = originalHeight >= 15 && originalHeight <= 8192;
            boolean isPixelValid = isWidthValid && isHeightValid;

            // 计算 Base64 编码后的大小（估算：原始字节数 * 4/3）
            double base64EstimatedSize = originalSize * 4.0 / 3.0;

            // 检查是否需要压缩
            if (isPixelValid && base64EstimatedSize <= 10 * 1024 * 1024) {
                // 原始文件完全符合要求
                return file;
            }

            // 需要压缩：调整尺寸和质量
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            double scale = 1.0;

            // 动态计算缩放比例（优先满足像素限制）
            if (originalWidth > 8192 || originalHeight > 8192) {
                scale = Math.min(8192.0 / originalWidth, 8192.0 / originalHeight);
            } else if (originalWidth < 15 || originalHeight < 15) {
                scale = Math.max(15.0 / originalWidth, 15.0 / originalHeight);
            }

            // 动态计算输出质量（优先满足 Base64 大小限制）
            double quality = 1.0;
            if (base64EstimatedSize > 10 * 1024 * 1024) {
                quality = (10 * 1024 * 1024 * 3.0 / 4.0) / originalSize;
                quality = Math.min(quality, 1.0); // 保证质量不超过 100%
            }

            // 执行压缩
            Thumbnails.of(file.getInputStream())
                    .scale(scale)
                    .outputQuality(quality)
                    .toOutputStream(outputStream);

            byte[] compressedBytes = outputStream.toByteArray();
            InputStream compressedInputStream = new ByteArrayInputStream(compressedBytes);

            // 生成新的 MultipartFile 对象
            MultipartFile compressedFile = new MockMultipartFile(
                    originalFilename,
                    originalFilename,
                    contentType,
                    compressedInputStream
            );

            // 日志记录
            log.info("源文件大小: {} B, 压缩后大小: {} B", originalSize, compressedFile.getSize());
            log.info("源文件尺寸: {}x{}, 压缩后尺寸: {}x{}",
                    originalWidth, originalHeight,
                    (int) (originalWidth * scale), (int) (originalHeight * scale));

            return compressedFile;
        } catch (Exception e) {
            log.error("图片压缩失败: {}", e.getMessage(), e);
            return null;
        }
    }

    @AutoLog(value = "注册表-获取openid")
    @ApiOperation(value = "注册表-获取openid", notes = "注册表-获取openid")
    @GetMapping(value = "/m/getOpenid")
    public Result<?> getOpenid(@RequestParam(name = "code", required = true) String code) {
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + APPID + "&secret=" + SECRET + "&code=" + code + "&grant_type=authorization_code";
        String s = doGet(url);
        log.info("response: {}", s);
        JSONObject jsonObject = JSONObject.parseObject(s);
        if (jsonObject.containsKey("openid")) {
            return Result.OK(jsonObject.getString("openid"));
        }
        return Result.error("失败");
    }

    private static String doGet(String url) {
        try {
            URL apiUrl = new URL(url);
            HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection();
            connection.setRequestMethod("GET");
            int responseCode = connection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                String inputLine;
                StringBuilder response = new StringBuilder();
                while ((inputLine = reader.readLine()) != null) {
                    response.append(inputLine);
                }
                reader.close();
                return response.toString();
            } else {
                return "";
            }
        } catch (IOException e) {
            log.error("doGet方法出现异常", e);
        }
        return "";
    }

    @GetMapping("/m/getImgFormObs")
    public ResponseEntity<byte[]> getImgFormObs(@RequestParam(name = "url") String url) throws IOException {
        String obsObjectName = getObsObjectName(url);
        InputStream input = ObsBootUtil.getObsFile(obsObjectName);
        // 使用 ByteArrayOutputStream 读取所有字节
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while ((len = input.read(buffer)) != -1) {
            baos.write(buffer, 0, len);
        }
        // 获取字节数组
        byte[] imageBytes = baos.toByteArray();
        // 关闭资源
        baos.close();
        input.close();
        // 设置响应头
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(getMediaTypeFromFileName(obsObjectName)); // 根据实际图片类型设置
        return new ResponseEntity<>(imageBytes, headers, HttpStatus.OK);
    }

    private MediaType getMediaTypeFromFileName(String fileName) {
        // 创建一个映射表，用于根据文件扩展名设置 Content-Type
        Map<String, MediaType> mediaTypes = new HashMap<>();
        mediaTypes.put("jpg", MediaType.IMAGE_JPEG);
        mediaTypes.put("jpeg", MediaType.IMAGE_JPEG);
        mediaTypes.put("png", MediaType.IMAGE_PNG);
        mediaTypes.put("gif", MediaType.IMAGE_GIF);

        // 获取文件扩展名
        String extension = getFileExtension(fileName);

        // 根据扩展名设置 Content-Type
        return mediaTypes.getOrDefault(extension, MediaType.APPLICATION_OCTET_STREAM);
    }

    private String getFileExtension(String fileName) {
        if (fileName == null || fileName.isEmpty()) {
            return "";
        }
        int dotIndex = fileName.lastIndexOf('.');
        if (dotIndex == -1) {
            return "";
        }
        return fileName.substring(dotIndex + 1).toLowerCase();
    }

    private String getObsObjectName(String url) {
        // 找到最后一个 '/'
        int lastSlashIndex = url.lastIndexOf('/');
        // 如果找不到最后一个 '/'，则返回原字符串
        if (lastSlashIndex == -1) {
            log.error("非法的路径: {}", url);
            throw new RuntimeException("非法的路径");
        }

        // 找到倒数第二个 '/'
        int secondLastSlashIndex = url.lastIndexOf('/', lastSlashIndex - 1);
        // 如果找不到倒数第二个 '/'，则返回最后一个 '/' 之后的内容
        if (secondLastSlashIndex == -1) {
            log.error("非法的路径: {}", url);
            throw new RuntimeException("非法的路径");
        }

        // 截取倒数第二个 '/' 之后的内容
        return url.substring(secondLastSlashIndex + 1);
    }


    @AutoLog(value = "罐车登记表-统计图卡片数据")
    @ApiOperation(value = "罐车登记表-统计图卡片数据", notes = "罐车登记表-统计图卡片数据")
    @PostMapping(value = "/reportCard")
    public Result<?> reportCard(@RequestBody ReportDTO reportDTO) {
        String startTime = reportDTO.getStartTime();
        String endTime = reportDTO.getEndTime();
        QueryWrapper<TankerRegistration> wrapper = Wrappers.query();
        wrapper.select("COUNT(*) AS total, COUNT(DISTINCT plate_no) AS totalUnique");
        wrapper.in("audit_status", 2, 4, 6, 8, 10, 11, 12, 13);
        wrapper.ge(StringUtils.isNotBlank(startTime), "create_time", startTime);
        wrapper.le(StringUtils.isNotBlank(endTime), "create_time", endTime);
        List<Map<String, Object>> maps = tankerRegistrationService.listMaps(wrapper);
        wrapper.clear();
        wrapper.select("COUNT(*) AS noPassTotal, COUNT(DISTINCT plate_no) AS noPassUnique");
        wrapper.in("audit_status", 3, 5);
        wrapper.ge(StringUtils.isNotBlank(startTime), "create_time", startTime);
        wrapper.le(StringUtils.isNotBlank(endTime), "create_time", endTime);
        List<Map<String, Object>> noPass = tankerRegistrationService.listMaps(wrapper);
        maps.addAll(noPass);
        wrapper.clear();
        wrapper.select("COUNT(*) AS unqualifiedTotal, COUNT(DISTINCT plate_no) AS unqualifiedUnique");
        wrapper.in("audit_status", 7, 9);
        wrapper.ge(StringUtils.isNotBlank(startTime), "create_time", startTime);
        wrapper.le(StringUtils.isNotBlank(endTime), "create_time", endTime);
        List<Map<String, Object>> unqualified = tankerRegistrationService.listMaps(wrapper);
        maps.addAll(unqualified);
        return Result.OK(maps);
    }

    @AutoLog(value = "罐车登记表-柱状图")
    @ApiOperation(value = "罐车登记表-柱状图", notes = "罐车登记表-柱状图")
    @PostMapping(value = "/reportBarQualified")
    public Result<?> reportBarQualified(@RequestBody ReportDTO reportDTO) {
        String startTime = reportDTO.getStartTime();
        String endTime = reportDTO.getEndTime();
        QueryWrapper<TankerRegistration> wrapper = Wrappers.query();
        wrapper.select("CONCAT(YEAR(create_time), '-', LPAD(MONTH(create_time), 2, '0')) AS month",
                "SUM(CASE WHEN audit_status IN (2, 4, 6, 8, 10, 11, 12, 13) THEN 1 ELSE 0 END) AS qualifiedCount",
                "COUNT(DISTINCT CASE WHEN audit_status IN (2, 4, 6, 8, 10, 11, 12, 13) THEN plate_no END) AS qualifiedDistinct");
        wrapper.ge(StringUtils.isNotBlank(startTime), "create_time", startTime);
        wrapper.le(StringUtils.isNotBlank(endTime), "create_time", endTime);
        wrapper.groupBy("month");
        List<Map<String, Object>> maps = tankerRegistrationService.listMaps(wrapper);
        return Result.OK(maps);
    }

    @AutoLog(value = "罐车登记表-柱状图")
    @ApiOperation(value = "罐车登记表-柱状图", notes = "罐车登记表-柱状图")
    @PostMapping(value = "/reportBarNoPass")
    public Result<?> reportBarNoPass(@RequestBody ReportDTO reportDTO) {
        String startTime = reportDTO.getStartTime();
        String endTime = reportDTO.getEndTime();
        QueryWrapper<TankerRegistration> wrapper = Wrappers.query();
        wrapper.select("CONCAT(YEAR(create_time), '-', LPAD(MONTH(create_time), 2, '0')) AS month",
                "SUM(CASE WHEN audit_status IN (3, 5) THEN 1 ELSE 0 END) AS noPassCount",
                "COUNT(DISTINCT CASE WHEN audit_status IN (3, 5) THEN plate_no END) AS noPassDistinct");
        wrapper.ge(StringUtils.isNotBlank(startTime), "create_time", startTime);
        wrapper.le(StringUtils.isNotBlank(endTime), "create_time", endTime);
        wrapper.groupBy("month");
        List<Map<String, Object>> maps = tankerRegistrationService.listMaps(wrapper);
        return Result.OK(maps);
    }

    @AutoLog(value = "罐车登记表-柱状图")
    @ApiOperation(value = "罐车登记表-柱状图", notes = "罐车登记表-柱状图")
    @PostMapping(value = "/reportBarUnQualified")
    public Result<?> reportBarUnQualified(@RequestBody ReportDTO reportDTO) {
        String startTime = reportDTO.getStartTime();
        String endTime = reportDTO.getEndTime();
        QueryWrapper<TankerRegistration> wrapper = Wrappers.query();
        wrapper.select("CONCAT(YEAR(create_time), '-', LPAD(MONTH(create_time), 2, '0')) AS month",
                "SUM(CASE WHEN audit_status IN (7, 9) THEN 1 ELSE 0 END) AS unQualifiedCount",
                "COUNT(DISTINCT CASE WHEN audit_status IN (7, 9) THEN plate_no END) AS unQualifiedDistinct");
        wrapper.ge(StringUtils.isNotBlank(startTime), "create_time", startTime);
        wrapper.le(StringUtils.isNotBlank(endTime), "create_time", endTime);
        wrapper.groupBy("month");
        List<Map<String, Object>> maps = tankerRegistrationService.listMaps(wrapper);
        return Result.OK(maps);
    }

    public MultipartFile addWatermarkToImage(MultipartFile file, String watermarkText) throws IOException {
        // 读取原始图片
        try (InputStream inputStream = file.getInputStream()) {
            BufferedImage originalImage = ImageIO.read(inputStream);
            if (originalImage == null) {
                throw new IllegalArgumentException("读取图片失败");
            }
            int imgWidth = originalImage.getWidth();
            int imgHeight = originalImage.getHeight();
            // 动态计算字体大小
            int fontSize = Math.min(imgWidth / 15, imgHeight / 15); // 可以根据需要调整这个比例
            // 设置水印字体和颜色
            Font font = new Font("Serif", Font.BOLD, fontSize);
            // 创建Graphics2D对象用于绘制水印
            Graphics2D g2d = originalImage.createGraphics();
            g2d.setFont(font);
            g2d.setColor(Color.RED);
            String s = DateUtils.formatDateTime();
            // 计算水印位置（这里选择右下角）
            FontMetrics fm = g2d.getFontMetrics();
            int textHeight = fm.getHeight();

            // 设置水印在左上角，并预留一些边距
            int margin = 10;
            int x = margin; // 从左边开始，距离左侧有一个边距
            int y = textHeight + margin; // 将文本放在顶部，距离顶部有一个边距

            // 绘制水印文字
            g2d.drawString(s, x, y); // 第一行
            g2d.drawString(watermarkText, x, y + textHeight); // 第二行

            // 清理资源
            g2d.dispose();

            // 确定图片格式
            String formatName = getFormatName(file.getContentType());

            // 将带有水印的图片写入输出流
            try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                boolean success = ImageIO.write(originalImage, formatName, outputStream);
                if (!success) {
                    throw new IOException("无法写入图片格式: " + formatName);
                }

                // 创建新的MultipartFile对象
                return new ByteArrayMultipartFile(
                        file.getName(),
                        file.getOriginalFilename(),
                        file.getContentType(),
                        outputStream.toByteArray()
                );
            }
        }
    }

    private static String getContentType(String fileName) {
        if (fileName == null || !fileName.contains(".")) {
            return "image/jpeg"; // 默认
        }
        String extension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
        switch (extension) {
            case "jpg":
            case "jpeg":
                return "image/jpeg";
            case "png":
                return "image/png";
            case "gif":
                return "image/gif";
            case "bmp":
                return "image/bmp";
            default:
                return "image/jpeg"; // 默认
        }
    }

    private String getFormatName(String contentType) {
        if (contentType != null && contentType.contains("/")) {
            String subtype = contentType.substring(contentType.indexOf("/") + 1);
            if ("jpeg".equalsIgnoreCase(subtype)) {
                return "jpg"; // 确保使用 ImageIO 支持的格式名
            }
            return subtype;
        }
        return "png";
    }

    private static String extractFileExtension(String fileName) {
        if (fileName == null || !fileName.contains(".")) {
            return ".jpg"; // 默认
        }

        return fileName.substring(fileName.lastIndexOf("."));
    }

    // 辅助类，用于创建新的MultipartFile对象
    public static class ByteArrayMultipartFile implements MultipartFile {
        private final String name;
        private final String originalFilename;
        private final String contentType;
        private final byte[] content;

        public ByteArrayMultipartFile(String name, String originalFilename, String contentType, byte[] content) {
            this.name = name;
            this.originalFilename = originalFilename;
            this.contentType = contentType;
            this.content = content;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public String getOriginalFilename() {
            return originalFilename;
        }

        @Override
        public String getContentType() {
            return contentType;
        }

        @Override
        public boolean isEmpty() {
            return content.length == 0;
        }

        @Override
        public long getSize() {
            return content.length;
        }

        @Override
        public byte[] getBytes() throws IOException {
            return content.clone();
        }

        @Override
        public InputStream getInputStream() throws IOException {
            return new ByteArrayInputStream(content);
        }

        @Override
        public void transferTo(File dest) throws IOException, IllegalStateException {
            try (FileOutputStream fos = new FileOutputStream(dest)) {
                fos.write(content);
            }
        }
    }

    @AutoLog(value = "罐车登记表-测试")
    @ApiOperation(value = "罐车登记表-测试", notes = "罐车登记表-测试")
    @PostMapping(value = "/test")
    public Result<?> test(@RequestParam(name = "createTime") String createTime) throws IOException {
        String backupsDir = "D:\\zclpj_tanker\\upload\\zclpj_backups\\poi";
        String filename = createTime + ".xlsx";
        String zipName = createTime + "zip.zip";
        List<String> urls = new ArrayList<>();
        List<TankerRegistration> tankerRegistrations = tankerRegistrationService.getBackups(createTime);
        for (TankerRegistration backup : tankerRegistrations) {
//            urls.addAll(extractUrls(backup.getHealthCertificate()));//健康证
            urls.addAll(extractUrls(backup.getLastTimeCleaningPhotos()));//上次清洗证明
            urls.addAll(extractUrls(backup.getHeadstockPhoto()));//车头照片
            urls.addAll(extractUrls(backup.getVehiclePhoto()));//车身照片
            urls.addAll(extractUrls(backup.getRearPhoto()));//车尾照片
            urls.addAll(extractUrls(backup.getTankPhoto()));//罐内照片
            urls.addAll(extractUrls(backup.getOilDischargePortPhoto()));//卸油口照片
            urls.addAll(extractUrls(backup.getLeadSealingPic()));//铅封照片
            urls.addAll(extractUrls(backup.getDocumentation()));//前三载证明文件
            urls.addAll(extractUrls(backup.getScreenshot()));//gps截图
            urls.addAll(extractUrls(backup.getDriverSign1()));//司机签字
            urls.addAll(extractUrls(backup.getDriverSign2()));//司机签字
            urls.addAll(extractUrls(backup.getDriverSign3()));//司机签字
//            urls.addAll(extractUrls(backup.getCertificate()));//产品合格证
//            urls.addAll(extractUrls(backup.getProductLabel()));//产品标签
//            urls.addAll(extractUrls(backup.getSamplingPhoto()));//液袋检验报告
            urls.addAll(extractUrls(backup.getPoundListPhoto1()));//入场磅单
            urls.addAll(extractUrls(backup.getLeadSealingPic1()));//入场铅封
            urls.addAll(extractUrls(backup.getOnboardRecordForm()));//随车装运记录
        }
        try {
            ExportParams exportParams = new ExportParams(createTime + "备份", "备份");
            exportParams.setImageBasePath(upLoadPath);
            Workbook workbook = ExcelExportUtil.exportExcel(exportParams, TankerRegistration.class, tankerRegistrations);
            File savefile = new File(backupsDir);
            if (!savefile.exists()) {
                boolean mkDirs = savefile.mkdirs();
                if (!mkDirs) {
                    throw new RuntimeException("创建文件夹失败");
                }
            }
            FileOutputStream fos = new FileOutputStream(backupsDir + "\\" + filename);
            workbook.write(fos);
            fos.close();

            // 获取文件大小并转换为KB
            Path filePath = Paths.get(backupsDir, filename);
            long fileSizeBytes = Files.size(filePath);
            int fileSizeKB = (int) (fileSizeBytes / 1024);

            MyScheduled.compressImagesToZip(urls, backupsDir + "\\" + zipName);

            // 获取文件大小并转换为KB
            Path zipPath = Paths.get(backupsDir, zipName);
            long zipSizeBytes = Files.size(zipPath);
            int zipSizeKB = (int) (zipSizeBytes / 1024);

            Backups backups = new Backups();
            backups.setFileName(filename).setFileUrl("zclpj_backups/poi/" + filename).setFileSize(fileSizeKB)
                    .setZipName(zipName).setZipUrl("zclpj_backups/poi/" + zipName).setZipSize(zipSizeKB)
                    .setBackupsTime(new Date()).setStatus(1);
            backupsService.save(backups);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        return Result.OK();
    }

    // 提取公共逻辑到一个辅助方法中
    private static List<String> extractUrls(String input) {
        if (input == null || input.trim().isEmpty()) {
            return Collections.emptyList(); // 或者根据需求返回其他默认值
        }
        return Arrays.stream(input.split(","))
                .map(String::trim) // 去除每个URL前后的空格
                .filter(url -> !url.isEmpty()) // 排除空字符串
                .collect(Collectors.toList());
    }

    public void addWatermarkToImage(File inputFile, File outputFile, String line1Text, String line2Text) throws IOException {
        // 读取原始图片
        try (InputStream inputStream = Files.newInputStream(inputFile.toPath())) {
            BufferedImage originalImage = ImageIO.read(inputStream);
            if (originalImage == null) {
                throw new IllegalArgumentException("读取图片失败");
            }
            int imgWidth = originalImage.getWidth();
            int imgHeight = originalImage.getHeight();
            // 动态计算字体大小
            int fontSize = Math.min(imgWidth / 15, imgHeight / 3); // 可以根据需要调整这个比例
            // 设置水印字体和颜色
            Font font = new Font("Serif", Font.BOLD, fontSize);
            // 创建Graphics2D对象用于绘制水印
            Graphics2D g2d = originalImage.createGraphics();
            g2d.setFont(font);
            g2d.setColor(Color.RED);

            // 计算水印位置（这里选择左上角）
            FontMetrics fm = g2d.getFontMetrics();
            int textHeight = fm.getHeight();
            int margin = 10; // 设置边距
            int x = margin; // 从左边开始，距离左侧有一个边距
            int y = textHeight + margin; // 将文本放在顶部，距离顶部有一个边距

            // 绘制水印文字
            g2d.drawString(line1Text, x, y); // 第一行
            g2d.drawString(line2Text, x, y + textHeight); // 第二行

            // 清理资源
            g2d.dispose();

            // 确定图片格式
            String formatName = "jpg"; // 根据需要调整图片格式

            // 将带有水印的图片写入输出文件
            ImageIO.write(originalImage, formatName, outputFile);
        }
    }

    @AutoLog(value = "罐车登记表-船")
    @ApiOperation(value = "罐车登记表-船", notes = "罐车登记表-船")
    @GetMapping(value = "/getShipTrack")
    public Result<?> getShipTrack(@RequestParam(name = "id") String id,
                                  @RequestParam(name = "beginTime") String beginTime,
                                  @RequestParam(name = "endTime") String endTime) throws IOException {
        String key = "a6b7c3f335cc455da2e66dc33b3a43ae";
        String baseUrl = "http://api.shipxy.com/apicall/GetShipTrack?enc=1&cut=0&jsf=func";
        String url = baseUrl + "&k=" + key + "&id=" + id;
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code: " + response);
            }

            log.info("response = {}", response);
            return Result.OK(response);
        }
    }

    /**
     * 将日期字符串转换为标准格式
     *
     * @param input 日期字符串
     * @return 转换后的日期字符串
     */
    public static String convertToStandardFormat(String input) {
        input = input.replaceAll(" ", "").trim();
        List<DateTimeFormatter> formatters = Arrays.asList(
                DateTimeFormatter.ofPattern("yyyy年M月d日"),
                DateTimeFormatter.ofPattern("yyyy年M月dd日")
        );
        for (DateTimeFormatter formatter : formatters) {
            try {
                LocalDate date = LocalDate.parse(input, formatter);
                return date.format(DateTimeFormatter.ISO_LOCAL_DATE); // 输出格式：yyyy-MM-dd
            } catch (Exception e) {
                // 如果解析失败，继续下一个格式
            }
        }
        return null;
    }

    @AutoLog(value = "罐车登记表-轨迹比对")
    @ApiOperation(value = "罐车登记表-轨迹比对", notes = "罐车登记表-轨迹比对")
    @PostMapping(value = "/GPSCompare")
    @Transactional(rollbackFor = Exception.class)
    public Result<?> GPSCompare(@RequestBody TankerRegistration tankerRegistration) {
        try {
            List<GpsRecord> gpsRecords = gpsRecordService.getByTankerId(tankerRegistration.getId());
            List<IdentifyRecord> records = identifyRecordService.getByTankerId(tankerRegistration.getId());

            // 获取"前一载"记录
            IdentifyRecord identifyRecord = records.stream()
                    .filter(r -> "前一载".equals(r.getSerialNumber()))
                    .findFirst()
                    .orElseThrow(() -> new BusinessException("无比对结果，请重新识别三载文件并保证图像质量"));

            // 解析并转换送货地和提货地坐标（WGS84转GCJ02）
            CoordinateParser.Doubles delivery = CoordinateParser.parse(identifyRecord.getDeliveryLonLat());
            CoordinateParser.Doubles pickup = CoordinateParser.parse(identifyRecord.getPickupLonLat());

            double[] deliveryGCJ = CoordinateConverter.wgs84ToGcj02(delivery.lon, delivery.lat);
            double[] pickupGCJ = CoordinateConverter.wgs84ToGcj02(pickup.lon, pickup.lat);

            // 初始化最小距离
            double minDeliveryDistance = Double.MAX_VALUE;
            double minPickupDistance = Double.MAX_VALUE;

            // 遍历所有GPS记录计算距离
            for (GpsRecord gpsRecord : gpsRecords) {
                // 解析并转换GPS坐标（原始值转GCJ02）
                CoordinateParser.Doubles gpsPoint = CoordinateParser.parseGpsRecord(
                        gpsRecord.getParkLon(),
                        gpsRecord.getParkLat()
                );

                // 计算距离
                double deliveryDistance = DistanceCalculator.calculate(
                        delivery.lon, delivery.lat,
                        gpsPoint.lon, gpsPoint.lat
                );

                double pickupDistance = DistanceCalculator.calculate(
                        pickup.lon, pickup.lat,
                        gpsPoint.lon, gpsPoint.lat
                );

                // 更新最小距离
                minDeliveryDistance = Math.min(minDeliveryDistance, deliveryDistance);
                minPickupDistance = Math.min(minPickupDistance, pickupDistance);
            }

            // 保存结果（保留两位小数）
            identifyRecord.setText3(String.format("%.2f", minDeliveryDistance));
            identifyRecord.setText4(String.format("%.2f", minPickupDistance));
            identifyRecordService.updateById(identifyRecord);

            return Result.OK("比对完成");
        } catch (BusinessException e) {
            log.error("业务异常: {}", e.getMessage());
            return Result.error(e.getMessage());
        } catch (Exception e) {
            log.error("系统异常: {}", e.getMessage(), e);
            return Result.error("系统处理异常");
        }
    }

    // 配置类（调整权重）
    private static final Map<Function<IdentifyRecord, String>, Integer> FIELD_WEIGHTS = new LinkedHashMap<Function<IdentifyRecord, String>, Integer>() {{
        put(IdentifyRecord::getProductType, 20);
        put(IdentifyRecord::getShipmentTime, 20);
        put(IdentifyRecord::getPickupLocation, 20);
        put(IdentifyRecord::getDeliveryLocation, 20);
        put(IdentifyRecord::getUnloadingTime, 20);
    }};

    // 算法接口
    @FunctionalInterface
    interface SimilarityAlgorithm {
        int calculateScore(String source, String target);
    }

    // 新算法实现
    private final SimilarityAlgorithm characterBasedAlgorithm = (source, target) -> {
        if (StringUtils.isBlank(source) || StringUtils.isBlank(target)) {
            return 0;
        }

        // 预先生成字符集合
        Set<Character> targetChars = target.chars()
                .mapToObj(c -> (char) c)
                .collect(Collectors.toSet());

        long matchedCount = source.chars()
                .mapToObj(c -> (char) c)
                .filter(targetChars::contains)
                .count();

        return (int) Math.round((double) matchedCount / source.length() * 100);
    };

    @AutoLog(value = "罐车登记表-AI比对")
    @ApiOperation(value = "罐车登记表-AI比对", notes = "罐车登记表-AI比对")
    @PostMapping(value = "/compare")
    @Transactional(rollbackFor = Exception.class)
    public Result<?> compare(@RequestBody TankerRegistration tankerRegistration) {
        List<IdentifyRecord> records = identifyRecordService.getByTankerId(tankerRegistration.getId());

        if (CollectionUtils.isEmpty(records)) {
            return Result.error("未找到比对记录");
        }

        records.forEach(this::calculateCharacterBasedScore);
        identifyRecordService.updateBatchById(records);

        return Result.OK("比对完成");
    }

    private void calculateCharacterBasedScore(IdentifyRecord record) {
        String textA = Optional.ofNullable(record.getText1()).orElse("");

        // 预先生成字符集合用于快速查找
        Set<Character> textAChars = textA.chars()
                .mapToObj(c -> (char) c)
                .collect(Collectors.toSet());

        int totalScore = FIELD_WEIGHTS.entrySet().stream()
                .mapToInt(entry -> {
                    String fieldValue = Optional.ofNullable(entry.getKey().apply(record)).orElse("");
                    return calculateFieldScore(fieldValue, textAChars) * entry.getValue() / 100;
                })
                .sum();

        record.setText2(String.valueOf(totalScore));
    }

    private int calculateFieldScore(String fieldValue, Set<Character> textAChars) {
        if (StringUtils.isBlank(fieldValue) || textAChars.isEmpty()) {
            return 0;
        }

        long matchedCount = fieldValue.chars()
                .mapToObj(c -> (char) c)
                .filter(textAChars::contains)
                .count();

        double ratio = (double) matchedCount / fieldValue.length();
        return (int) Math.round(ratio * 100);
    }

    @AutoLog(value = "罐车登记表-AI辅助")
    @ApiOperation(value = "罐车登记表-AI辅助", notes = "罐车登记表-AI辅助")
    @PostMapping(value = "/assistant")
    @Transactional(rollbackFor = Exception.class)
    public Result<?> assistant(@RequestBody TankerRegistration tankerRegistration) {
        try {
            // 1. 参数校验
            validateDocumentationFiles(tankerRegistration);

            // 2. 处理三载证明文件
            List<IdentifyRecord> identifyRecords = processDocumentationFile(tankerRegistration.getDocumentation());

            // 3. 处理卸货照片并关联数据
            processUnloadingPhotos(tankerRegistration, identifyRecords);

            identifyRecordService.removeByTankerId(tankerRegistration.getId());

            IdentifyRecord identifyRecord = identifyRecords.get(0);
            String deliveryLocation = Optional.ofNullable(identifyRecord.getDeliveryLocation()).orElse("");
            String pickupLocation = Optional.ofNullable(identifyRecord.getPickupLocation()).orElse("");
            String baseUrl = "https://restapi.amap.com/v5/place/text?key=b22adbd041f6691dae2394f44d93235b&keywords=";
            JSONObject deliveryResponse = RestUtil.get(baseUrl + deliveryLocation);
            log.info("deliveryResponse: {}", deliveryResponse);
            String deliveryStatus = deliveryResponse.getString("status");
            if ("1".equals(deliveryStatus)) {
                JSONArray pois = deliveryResponse.getJSONArray("pois");
                JSONObject o = pois.getJSONObject(0);
                identifyRecord.setDeliveryLonLat(o.getString("location"));
            }
            JSONObject pickupResponse = RestUtil.get(baseUrl + pickupLocation);
            log.info("pickupResponse: {}", pickupResponse);
            String pickupStatus = pickupResponse.getString("status");
            if ("1".equals(pickupStatus)) {
                JSONArray pois = pickupResponse.getJSONArray("pois");
                JSONObject o = pois.getJSONObject(0);
                identifyRecord.setPickupLonLat(o.getString("location"));
            }

            // 4. 批量保存数据
            identifyRecordService.saveBatch(identifyRecords);

            return Result.OK("操作成功");
        } catch (BusinessException e) {
            return Result.error(e.getMessage());
        } catch (Exception e) {
            log.error("系统处理异常", e);
            return Result.error("系统处理异常，请稍后重试");
        }
    }

    //-------------------- 模块化方法 --------------------
    private void validateDocumentationFiles(TankerRegistration reg) {
        try {
            validateFileCount(reg.getDocumentation(), 1, "三载证明文件");
            validateFileCount(reg.getLastTimeUnloadingPhotos(), 3, "三载卸货磅单照片");
        } catch (BusinessException e) {
            throw new RuntimeException(e);
        }
    }

    private void validateFileCount(String files, int expectedCount, String fieldName) throws BusinessException {
        if (StringUtils.isBlank(files)) {
            throw new BusinessException(fieldName + "不能为空");
        }
        int actualCount = files.split(",").length;
        if (actualCount != expectedCount) {
            throw new BusinessException(fieldName + "数量必须为【" + expectedCount + "】");
        }
    }

    private List<IdentifyRecord> processDocumentationFile(String documentation) throws BusinessException {
        try {
            String fileUrl = documentation.split(",")[0];
            RecognizeGeneralTableResponse response = HuaweiCloudUtil.recognizeGeneralTable(fileUrl);
            return handleDocumentationTableDataOfOcr(response);
        } catch (Exception e) {
            log.error("三载证明文件解析失败: {}", documentation, e);
            throw new BusinessException("三载证明文件解析失败，请检查文件内容");
        }
    }

    private void processUnloadingPhotos(TankerRegistration tankerRegistration, List<IdentifyRecord> records) throws BusinessException {
        String photos = tankerRegistration.getLastTimeUnloadingPhotos();
        String[] photoUrls = photos.split(",");
        // 校验记录数量匹配
        if (records.size() < photoUrls.length) {
            throw new BusinessException("识别记录与照片数量不匹配");
        }
        for (int i = 0; i < photoUrls.length; i++) {
            try {
                String ocrResult = HuaweiCloudUtil.handleGeneralTableDataOfOcr(
                        HuaweiCloudUtil.recognizeGeneralTable(photoUrls[i])
                );
                records.get(i).setText1(ocrResult).setTankerId(tankerRegistration.getId());
            } catch (Exception e) {
                log.error("第{}张卸货照片处理失败: {}", i + 1, photoUrls[i], e);
                throw new BusinessException("第" + (i + 1) + "张卸货照片解析失败");
            }
        }
    }

    @AutoLog(value = "罐车登记表-查询gps停留记录")
    @ApiOperation(value = "罐车登记表-查询gps停留记录", notes = "罐车登记表-查询gps停留记录")
    @PostMapping(value = "/GPSSearch")
    public Result<?> GPSSearch(@RequestBody TankerRegistration tankerRegistration) {
        String plateNo = tankerRegistration.getPlateNo();
        Date lastTimeUnloadingDate1 = tankerRegistration.getLastTimeUnloadingDate1();
        LocalDateTime originalDate = lastTimeUnloadingDate1.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime();
        LocalDateTime beginDateTime = originalDate.minusDays(1)
                .withHour(0)
                .withMinute(0)
                .withSecond(0);
        LocalDateTime endDateTime = originalDate
                .withHour(23)
                .withMinute(59)
                .withSecond(59);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String s = DateUtils.formatDate(lastTimeUnloadingDate1, "yyyy-MM-dd");
        Map<String, String> params = new HashMap<>();
        params.put("plateNo", plateNo);
        params.put("begintime", beginDateTime.format(formatter));
        params.put("endtime", endDateTime.format(formatter));
        params.put("plateColor", "2");
        try {
            // 使用 OkHttp
            String response = HuaweiCloudUtil.sendPost("https://enviroment.tw-tj.com:2443/NewElefence/GetRoutePath", params);
            System.out.println("Response: " + response);
            JSONObject jsonObject = JSONObject.parseObject(response);
            int status = jsonObject.getInteger("status");
            if (1001 == status) {
                List<GpsRecord> list = new ArrayList<>();
                JSONObject result = jsonObject.getJSONObject("result");
                JSONArray parkArray = result.getJSONArray("parkArray");
                for (Object park : parkArray) {
                    JSONObject parkObject = (JSONObject) park;
                    GpsRecord gpsRecord = parkObject.toJavaObject(GpsRecord.class);
                    gpsRecord.setTankerId(tankerRegistration.getId());
                    list.add(gpsRecord);
                }
                if (!list.isEmpty()) {
                    LambdaQueryWrapper<GpsRecord> wrapper = Wrappers.lambdaQuery();
                    wrapper.eq(GpsRecord::getTankerId, tankerRegistration.getId());
                    gpsRecordService.remove(wrapper);
                    gpsRecordService.saveBatch(list);
                }
            }
        } catch (IOException e) {
            log.error("GPS查询失败", e);
        }
        return Result.OK("操作成功！");
    }


}
